今天是星期五,晚上公司有個聚餐(資深
老新人的歡迎會),所以今天要生文章出來時間蠻吃緊的,
只好晚上熬夜稍微寫一點(
其實是睡不著拖太晚),另外星期五也是總結一週成果的時間點,
這星期都在開會、研討會、課程、
寫鐵人賽文章,多少還是要顧一下工作上的進度和成果啊,
鐵人賽的文章比想像中還要花更多時間呢。
這也是一個被大量廣泛應用的模式,而且實行起來很簡單,程式碼很短,但是有很多小細節需要注意(這些細節會因為語言而不同),不然可能會有難以發現的bug藏在裡面。
定義:只有一個實例,而且自行實例化並向整個系統提供這個實例。
屬於創建模式,
這個模式涉及到一個單一的類別,他必須要
創建自己的實例
,並且
確保只有單一個對象被創建
。這個類別
提供一個方法
訪問其被創建的唯一一個對象。
存取IO和資料庫等資源,這時候要考慮使用單例模式。
試著實現一個積極單例模式
public class SingleObject {
//創建 SingleObject 的一個對象
private static SingleObject instance = new SingleObject();
//讓構造函數為 private,這樣該類就不會被實例化
private SingleObject(){}
//獲取唯一可用的對象
public static SingleObject getInstance(){
return instance;
}
}
試著實現一個懶散單例模式
積極模式在宣告靜態物件的時候就已經初始化,
但是懶散模式(Lazy)在呼叫getInstance時才進行初始化。
public class Singleton{
private static Singleton instance;
//私有的建構式讓別人不能創造
private Singleton (){}
//因為整個系統都要存取這個類別,很可能有多個process或thread同時存取
//為了讓線程安全添加synchronized在多線程下確保物件唯一性
public static synchronized Singleton getInstance(){
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
但是這個實現方式每次都需要進行同步,效率會很很低。
試著用雙重鎖實現
public class Singleton {
public static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
// 第一層判斷為了避免不必要的同步
if(instance == null){
synchronized (Singleton.class){
// 第二層判斷為了在null的狀況下建立實例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
判斷兩次看起來有點奇怪,但其實這樣做是有原因的。
instance = new Singleton();
上面這段程式碼看起來只有一段,但其實他不是原子操作,這句程式碼會被編譯成多條組合指令,大致上他做了三件事:
但是由於Java編譯器允許失序執行,所以 2. 和 3. 的順序是無法保證的,有可能 1-2-3 也有可能 1-3-2 。如果在 3. 執行完畢、2. 還沒執行之前,切換到線程B,那instance已經不是null,此時B取走instance再使用就會出錯。
JDK1.5以後的版本,官方注意到問題,所以調整JMM具體化volatile關鍵字,所以只要把instance寫法改成
private volatile static Singleton instance = null;
就可以保證都從主記憶體讀取,並且以DCL寫法完成單例模式。
雖然偶爾會失效但是DCL還是運用最多的模式。
試著用靜態內部類實現
public class StaticInnerClass {
private StaticInnerClass(){}
public static StaticInnerClass getInstance(){
return StaticInnerClassHolder.instance;
}
/**
* 靜態的內部類別
*/
private static class StaticInnerClassHolder{
private static StaticInnerClass instance = new StaticInnerClass();
}
}
可以確保線程安全,保證物件唯一性,並且延遲實例化,所以推薦使用。
用列舉實現
public enum EnumSingleton {
INSTANCE;
public void doSomething(){
System.out.println("do do !");
}
}
可以避免反實例化。
前面的單例模式要避免反實例化要加入readResolve()方法
private Object readResolve() throws ObjectStreamException {
return instance;
}
這是提供給開發人員控制物件的反序列化方法。
產品和工廠介面
public abstract class Product {
public String getName(){
return this.getClass().getSimpleName();
}
}
public interface Factory {
public Product getProduct();
}
可樂和漢堡(繼承了getName方法)
public class Cola extends Product {
}
public class Humberger extends Product {
}
SingletonFactory
public class SingletonFactory {
public static Factory getColaFactory(){
return ColaFactory.colaFactory;
}
public static Factory getHumbergerFactory(){
return HumbergerFactory.humbergerFactory;
}
private static class ColaFactory implements Factory{
private static ColaFactory colaFactory = new ColaFactory();
private ColaFactory(){}
@Override
public Product getProduct() {
return new Cola();
}
}
private static class HumbergerFactory implements Factory{
private static HumbergerFactory humbergerFactory = new HumbergerFactory();
private HumbergerFactory(){}
@Override
public Product getProduct() {
return new Humberger();
}
}
}
測試一下拿到可樂和漢堡產品
public class Test {
public void test(){
Cola cola = (Cola) SingletonFactory.getColaFactory().getProduct();
Humberger humberger =(Humberger) SingletonFactory.getHumbergerFactory().getProduct();
System.out.println(cola.getName());
System.out.println(humberger.getName());
}
}
拿到的數值
Cola
Humberger
今天關於單例模式就練習到這邊,如果有問題或建議歡迎留言或寄訊息給我~